O curso ocorrerá entre 13 e 15 de dezembro e está estruturado assim:
Para fazer download dos dados utilizados na aula acesse esta pasta do dropbox.
Todas as aulas ainda podem ser alteradas. Exercícios seão realizados em sala para fixar as filosofias dos pacotes e seus comandos.
Que tal já utilizar um pouco dos seus conhecimentos? Tente realizar o exercício abaixo. Caso não consiga realizar as duas primeiras tarefas, revisite o curso introdutório. A terceira é um desafio.
O R vem como algumas funções nativas para ler dados. Entre elas as mais comuns são:
| Função | tipo de arquivo |
|---|---|
| read.table() | .txt, .csv, .tsv |
| read.csv() | .csv (sep = ,) |
| read.csv2() | .csv (sep = ;) |
| read.fwf() | variáveis com tamanhos fixos |
| muito usado em microdados | |
| readLines() | lê linhas como são |
Vamos testar estas funções?
csv <- read.csv2("teste.csv")
str(csv) # Textos se tornaram fatores
## 'data.frame': 10000 obs. of 2 variables:
## $ texto: Factor w/ 25 levels "a","b","c","d",..: 1 2 3 4 5 6 7 8 9 10 ...
## $ num : int 1 2 3 4 5 6 7 8 9 10 ...
csv2 <- read.csv2("teste.csv",
stringsAsFactors = FALSE)
str(csv2)
## 'data.frame': 10000 obs. of 2 variables:
## $ texto: chr "a" "b" "c" "d" ...
## $ num : int 1 2 3 4 5 6 7 8 9 10 ...
| Função | tipo de arquivo | Quando usar? |
|---|---|---|
| saveRDS() | .RDS | salvar um objeto |
| readRDS() | .RDS | ler um objeto |
| save() | .RData ou .RDA | salvar vários objetos |
| load() | .RData ou .RDA | ler vários objetos |
RDS <- readRDS("teste.RDS")
str(RDS)
## 'data.frame': 10000 obs. of 2 variables:
## $ texto: chr "a" "b" "c" "d" ...
## $ num : int 1 2 3 4 5 6 7 8 9 10 ...
load("teste.RDA") # Não precisa atribuir
str(RDA)
## 'data.frame': 10000 obs. of 2 variables:
## $ texto: chr "a" "b" "c" "d" ...
## $ num : int 1 2 3 4 5 6 7 8 9 10 ...
Para que eu preciso de tantas formas de ler e escrever dados??
Bem, primeiramente . fora Temer. Segundo, você geralmente não controla o formato do arquivo que conterá os dados que você usará. Por isso é importante conhecer variadas formas ler os mais variados formatos.
O R puro - isto é, assim que você abre ele - não lê arquivos com as extensões clássicas do excel .xls e .xlsx. Contudo, tem muitos pacotes que permitem ler e escrever arquivos com estes formatos.
| Pacote | Bônus | Ônus |
|---|---|---|
readxl |
muito rápido | não exporta arquivos |
xlsx |
exporta arquivos | trava frequentemente |
| (acesso por Java) | ||
openxlsx |
rápido, le e | |
| exporta arquivos | mais lento que readxl |
Mesmo assim, não recomendo usar .xlsx por dois motivos: i) Há um limite de 1 mihão de linhas e ii) até com os bons pacotes, demora a gerar o arquivo.
Além das funções de leitura read.*, Há também funções para escrever arquivos: write.*. Os sufixos se repetem nas duas famílias de funções.
| Função | tipo de arquivo |
|---|---|
| write.table() | .txt, .csv, .tsv |
| write.csv() | .csv (sep = ,) |
| write.csv2() | .csv (sep = ;) |
| writeLines() | escreve linhas como são |
| openxlsx::write.xlsx() | .xlsx |
Até as melhores opções para escrever um .xlsx são mais lentas que o R básico.
library(microbenchmark)
library(openxlsx)
microbenchmark(times = 30,
xlsx = write.xlsx(RDA, "teste.xlsx"),
csv = write.csv2(RDA, "teste2.csv"))
## Unit: milliseconds
## expr min lq mean median uq max neval
## xlsx 308.4 400.7 502.3 458.3 524.6 1376.4 30
## csv 22.6 25.3 36.2 32.4 41.2 74.4 30
microbenchmark(times = 30, unit = "ms",
csv = read.csv2("teste.csv"),
RDA = load("teste.RDA"),
RDS = readRDS("teste.RDS"),
readxl = readxl::read_excel("teste.xlsx"),
openxlsx = read.xlsx("teste.xlsx"))
## Unit: milliseconds
## expr min lq mean median uq max neval
## csv 8.71 9.72 11.68 11.26 12.27 16.5 30
## RDA 3.48 3.66 4.52 4.01 4.73 10.1 30
## RDS 3.18 3.47 5.33 3.98 7.04 16.4 30
## readxl 33.70 35.89 59.87 38.92 44.33 592.4 30
## openxlsx 109.78 125.50 159.22 148.69 185.00 274.1 30
Salvamos os mesmo dados em variados formatos. Vejamos a diferença de tamanho entre eles:
file.size("teste.csv") / 2^10 # em Kb
## [1] 96.6
file.size("teste.RDS") / 2^10 # em Kb. 6 vezes menor!
## [1] 21.1
file.size("teste.RDA") / 2^10 # em Kb. Igual!
## [1] 21.1
file.size("teste.xlsx") / 2^10 # em Kb
## [1] 124
O pacote haven possui funções para ler formatos de outros softwares como STATA, SPSS e SAS
| software | função |
|---|---|
| SAS | write_sas |
| SAS | read_sas |
| Stata | write_dta |
| Stata | read_dta |
| SPSS | write_sav |
| SPSS | read_sav |
Há inumeros pacotes para ler dados armazenados em bases de dados. Alguns são:
| pacote | Base |
|---|---|
| RMySQL | MySQL |
| RDOBC | SQL Server |
| RPostgres | Postgres |
| RSQLite | SQLite |
| rmongodb | MongoDB |
| RCassandra | Cassandra |
O curso online realizado já cobriu um pouco da sintaxe de manipulação de dados. Vamos das principais funções.
Toda análise de dados começa importando os dados:
load('Microdados/Censo_Edu_Superior_2014.RDA')
ls() # quais objetos foram carregados
## [1] "docente2014" "grad2014" "ies2014" "local2014"
| Função | ação |
|---|---|
| str() | estrutura dos dados |
| summary() | sumário dos dados, de acordo com a classe |
| class() | classe do objeto |
| length() | tamanho do objeto |
| names() | nomes do objeto |
Tente usar a função str() nas tabelas importadas:
str(docente2014)
str(grad2014)
str(ies2014)
str(local2014)
Agora vamos conhecer um pouco de algumas variáveis. Tente os comandos abaixo e observe as os resultados no console.
summary(factor(docente2014$DS_CATEGORIA_ADMINISTRATIVA))
# conta as categorias
summary(grad2014$QT_VAGAS_NOVAS_EAD)
# sumário de 6 números
summary(local2014$DS_LOGRADOURO)
# informa ser texto
summary(ies2014)
# sumário de todas variáveis
class(docente2014) # data.frame
class(grad2014$QT_CONCLUINTE_CURSO) # inteiro
length(local2014) # conta variáeis
length(local2014$CO_IES) # quantidade de observações
names(ies2014) # nomes das variáveis
names(docente2014) # nomes das variáveis
Há também a opçao de você ver apenas algumas observações com as funções head(objeto, quantidade) e tail(objeto, quantidade)
head(grad2014$QT_CONCLUINTE_CURSO)
tail(grad2014$QT_CONCLUINTE_CURSO)
# 6 por padrão
head(ies2014$VL_RECEITA_PROPRIA, 10)
# primeiros 10
tail(docente2014$DS_ESCOLARIDADE_DOCENTE, 3)
# 3 últimos
Como observamos até agora todas as tabelas são grandes. Para este exercício só precisamos de algumas variáveis. Mas como fazer isso? O R básico nós da algumas opções:
[[[$A primeira forma de realizar uma seleção é colocar colchetes até o objeto e, dentro dos colchetes, inserir o nome ou indice do valor desejado. No caso de data frames podemos indicar as linhas e colunas que desejamos.
data.frame[linhas, colunas]
docente2014[1:1000 , c("NO_IES",
"DS_SEXO_DOCENTE",
"IN_SUBSTITUTO")]
# Apenas as primeiras 1000 observações
# das 3 variáveis selecionadas
docente2014[1:10 , ] # 10 observações
docente2014[ ,c("NO_IES",
"DS_SEXO_DOCENTE",
"IN_SUBSTITUTO")]
# todas observações das 3 variáveis
A seleção com os colchetes duplos escolhe apenas um elemento do objeto. Também são aceitos tanto o índice quanto o nome.
objeto[[elemento]]
Observe que enquanto [] retorna um data.frame, [[]] retorna apenas o vetor.
ultima <- grad2014[[length(grad2014)]]
# usando número
laboratorio <- grad2014[["IN_POSSUI_LABORATORIO"]]
# usando nome
laboratorio[[500]]
## [1] 1
# apenas a 500ª observação
O $ funciona de modo semelhante ao [[]], isto é, retorna apenas um elemento. Mas há uma diferença, o $ só aceita nomes para realizar as seleções.
lab <- grad2014$IN_POSSUI_LABORATORIO
all.equal(lab, laboratorio) # é igual
## [1] TRUE
docente2014$1 # erro
## Error: <text>:1:13: unexpected numeric constant
## 1: docente2014$1
## ^
A sintaxe da função é como segue:
subset(x, subset, select, drop = FALSE, ...)
subst <- subset(docente2014,
subset = IN_SUBSTITUTO == 1,
select = c("DS_SEXO_DOCENTE", "NU_IDADE_DOCENTE"))
subst2 <- subset(docente2014,
subset = IN_SUBSTITUTO == 1,
select = c(17, 21))
all.equal(subst, subst2)
## [1] TRUE
A funçao with() tira a necessidade de ficar digitando a cada vez o nome do objeto principal. Assim,
plot(ies2014$VL_RECEITA_PROPRIA,
(ies2014$QT_TEC_DOUTORADO_MASC + ies2014$QT_TEC_DOUTORADO_FEM))
pode ser reescrita como
with(ies2014,
plot(x = QT_TEC_DOUTORADO_MASC + QT_TEC_DOUTORADO_FEM,
y = VL_RECEITA_PROPRIA))
| Função | ação |
|---|---|
| lapply() | executa uma função em cada elemento |
| sapply() | idem, resultado simplificado |
| tapply() | iden, dado um critério |
| aggregate() | agrega por algum critério |
| transform() | cria variável calculada |
As funções lapply() e sapply() executam uma mesma função para para cada elemento de um objeto. No caso dos data frmaes, a função será aplicada em cada variável.
A diferença entre as duas está na objeto que retornam: enquanto lapply retorna uma sempre lista, sapply tenta simplificar o resultado para um vetor, matriz ou mesmo um data frame.
classe <- sapply(ies2014, class)
# classe de todas viariáveis
sapply(ies2014[, classe == "numeric"],
mean) /1000000 # em milhões
## VL_RECEITA_PROPRIA VL_TRANSFERENCIA
## 102.734132 33.833887
## VL_OUTRA_RECEITA VL_DES_PESSOAL_REM_DOCENTE
## 23.320635 37.546380
## VL_DES_PESSOAL_REM_TECNICO VL_DES_PESSOAL_ENCARGO
## 18.606625 22.393972
## VL_DES_CUSTEIO VL_DES_INVESTIMENTO
## 41.580863 9.319011
## VL_DES_PESQUISA VL_DES_OUTRAS
## 1.253679 15.526447
# média só dos vetores numéricos
A função tapply() é da mesma família, mas ela leva um argumento a mais, um índice. É como se pedíssemos Execute FUN no vetor X para cada categoria de INDEX
tapply(ies2014$VL_RECEITA_PROPRIA,
INDEX = ies2014$DS_CATEGORIA_ADMINISTRATIVA,
FUN = mean)/1000000 # em milhões
## Especial Privada com fins lucrativos
## 49.56429 140.34667
## Privada sem fins lucrativos Pública Estadual
## 87.62236 23.75046
## Pública Federal Pública Municipal
## 44.02261 11.72985
Para ter a média por categoria de instituição para todas as variáveis numéricas bosta aplicar o tapply() em cada uma das variáveis numéricas:
sapply(ies2014[, classe == "numeric"], FUN = tapply,
INDEX = ies2014$DS_CATEGORIA_ADMINISTRATIVA,
mean)
# Cuidado com o nome dos argumentos
# tanto tapply quanto apply tem FUN como argumento
Antes de mais nada é necessário baixa e carregar o pacote.
install.packages('dplyr') # nome como texto
library(dplyr)
O dplyr é pensado na forma de uma gramática em que as principais tarefas de manipulação de data frames foi transformada em funções. São elas:
Isto não é um cachimbo.
Outra ponto chave na facilidade da sintaxe do dplyr é o %>% (pipe operator). Ele torna a construção do código mais parecida a nossa linguagem ao tornar o objeto que o precede como primeiro argumento da função que o segue. Ou seja, ele não faz nada, não é um operador! Vejamos:
Mostre uma tabela com a escolaridade dos 30 últimos professores substitutos da UFSC:
table(
tail(docente2014[
docente2014$NO_IES == "UNIVERSIDADE FEDERAL DE SANTA CATARINA" &
docente2014$IN_SUBSTITUTO == 1,
"DS_ESCOLARIDADE_DOCENTE"], 30))
Mostre uma tabela com a escolaridade dos 30 últimos professores substitutos da UFSC:
docente2014 %>%
filter(NO_IES == "UNIVERSIDADE FEDERAL DE SANTA CATARINA",
IN_SUBSTITUTO == 1) %>%
select(DS_ESCOLARIDADE_DOCENTE) %>% tail(30) %>% table()
Antes de seguirmos vamos limpar nossa área de trabalho e carregar os dados de domicílios da PNAD.
rm(list = ls())
load('Microdados/domicilios_2014.RDA')
dim(domicilios_2014) # mais de 150 mil observações!
## [1] 151291 83
Observação: A PNAD apresenta dados de amostra e as generalizações devem ser cuidadosas.
O verbo select() permite selecionar apenas uma variável (coluna) da tabela. Isto é muito necessário quando trabalhamos microdados porque raramente você precisará das 83 variáveis que compõe a tabela domicílios.
Depois de verificar o dicionário de variáveis da pesquisa, decidirmos fica com as seguintes variáveis:
| NOME | conteúdo | NOME | conteúdo |
|---|---|---|---|
| V0102 | numero de controle | V0104 | tipo de entrevista, |
| V0105 | total de moradores | V0201 | espécie de domicílio, |
| V0205 | número de cômodos | V0208 | aluguel, |
| V0209 | prestação | V0230 | tem maquina, |
| V02321 | tem tablet | V4621 | renda mensal per capta |
Os argumentos do select são: i) os dados e ii) as colunas selecionadas. As colunas não precisam ser escritas entre aspas. É possível renomear as colunas ao selecionar.
domicilios <- domicilios_2014 %>%
select(controle = V0102, tipo = V0104, moradores = V0105,
domicílio = V0201, cômodos = V0205,
aluguel = V0208, prestação = V0209,
maquina = V0230, tablet = V02321, renda = V4621)
length(domicilios) # só as 10 variáveis que escolhemos
## [1] 10
Além da facilidade visual e de sintaxe, o select() apresenta algumas outras forma interessantes de selecionar as variáveis desejadas. Digite ?select e dê uma olhada nos exemplo oferecidos.
domicilios %>% select(domicílio:prestação)
domicilios %>% select(contains('a'))
domicilios %>% select(starts_with("m"))
Outra função relaciona à variáveis é a mutate. Ela permite criar novas variáveis para cada observação utilizando outras variáveis da tabela. Ela é similar a função transform() do R-base, mas a mutate pode usar na mesma chamada variáveis que ela está criando.
O Código da UF dos domicíos está escondido nos primeiros dois números das variável de controle. Vamos criar a variável UF
domicilios <- domicilios %>%
mutate(UF = substr(controle, 1, 2), SC = UF == 42)
str(domicilios)
## Classes 'tbl_df', 'tbl' and 'data.frame': 151291 obs. of 12 variables:
## $ controle : chr "11000015" "11000015" "11000015" "11000015" ...
## $ tipo : chr "01" "01" "01" "01" ...
## $ moradores: chr "03" "02" "01" "05" ...
## $ domicílio: chr "1" "1" "1" "1" ...
## $ cômodos : int 3 6 3 12 5 5 7 5 NA 6 ...
## $ aluguel : num 376 NA NA NA NA NA NA NA NA NA ...
## $ prestação: num NA NA NA NA NA NA NA NA NA NA ...
## $ maquina : chr "4" "2" "2" "2" ...
## $ tablet : chr "4" "4" "4" "2" ...
## $ renda : num 500 1150 724 1700 855 ...
## $ UF : chr "11" "11" "11" "11" ...
## $ SC : logi FALSE FALSE FALSE FALSE FALSE FALSE ...
A função filter() recebe dois argumentos: i) a tabela que será filtrada e ii) os critérios de filtragem. Estes critérios devem ser de tipo lógico e podem ser quantos forem necessários.
vamos filtrar apenas os dados dos domicílios de Santa Catarina
dom_SC <- domicilios %>% filter(UF == 42)
dom_SC2 <- domicilios %>% filter(SC)
identical(dom_SC, dom_SC2)
## [1] TRUE
nrow(dom_SC)
## [1] 4484
Se houver mais de um predicado, eles serão unidos por &. Vamos pegar apenas as observações dos domicílios de SC que tinham tablet.
tablet_SC <- domicilios %>%
filter(UF == 42,
tablet == 2) # 2 é sim, dicionário da pesquisa
(nrow(tablet_SC) / nrow(dom_SC)) %>% round(2) # %
## [1] 0.12
A função summarise() cria um resumo (sumário) dos dados. Mas qual resumo? O que você solicitar. A característica principal desta função é que ela ela retornará apenas um resultado. Por exemplo: qual a menor renda domicíliar de Santa Catarina? E a maior? Qual o número de pessoas que não tem máquina de lavar? Assim por diante
Seus argumentos são: i) a tabela e ii) Como resumir os dados.
dom_SC %>%
summarise(moradores = sum(as.numeric(moradores),
na.rm = TRUE), # nomes iguais, sem problemas
minima = min(renda, na.rm = TRUE),
maxima = max(renda, na.rm = TRUE),
maquina = sum(maquina == 4, na.rm = TRUE))
## # A tibble: 1 × 4
## moradores minima maxima maquina
## <dbl> <dbl> <dbl> <int>
## 1 9701 0 999999999999 520
arrange() organiza os dados baseado em uma das variáveis. Por padrão arrange() usa a ordem crescente, mas se você quiser fazê-lo pela ordem decrescente basta usar o adjetivo desc().
dom_SC %>% select(cômodos, renda) %>%
arrange(renda) %>% head()
## # A tibble: 6 × 2
## cômodos renda
## <int> <dbl>
## 1 8 0
## 2 5 0
## 3 5 0
## 4 6 0
## 5 10 0
## 6 4 0
# Ou os de maior renda
dom_SC %>% select(cômodos, renda) %>%
arrange(desc(renda)) %>% head()
## # A tibble: 6 × 2
## cômodos renda
## <int> <dbl>
## 1 9 999999999999
## 2 3 999999999999
## 3 8 999999999999
## 4 7 999999999999
## 5 3 999999999999
## 6 7 999999999999
# Parece ter algo errado com o renda. Leia a documentação
Até agora nossa análise tem sido muito genérica considerando toda a população de Santa Catarina como uma coisa única. Seria bom poder separá-la em grupos ou classes, não? A estratégia de Separar os dados, Aplicar um regra e Combinar eles visa permitir esse tipo de análise sem perder o todo de vista. E o dplyr oferece um verbo fundamental para usar esta estratégia: group_by()
A função group_by(), diferentemente das outas, não tem utilidade sozinha. Ela cria grupos de dados que compreendam todas as combinação dos critérios que passarmos. Se você quiser remover os grupos da tabela (apenas um atributo), use ungroup().
Eliminaremos as rendas de 999999999999, pois represetam não reclaração.
dom_SC %>% filter(renda != 999999999999) %>%
group_by(maquina, tablet) %>%
summarise(minima = min(renda),
media = mean(renda),
maxima = max(renda),
quantidade = n())
## Source: local data frame [5 x 6]
## Groups: maquina [?]
##
## maquina tablet minima media maxima quantidade
## <chr> <chr> <dbl> <dbl> <dbl> <int>
## 1 2 2 0 2247.8511 20000 470
## 2 2 4 0 1449.8897 23000 2320
## 3 4 2 316 1286.2800 3000 25
## 4 4 4 0 960.6358 7700 486
## 5 <NA> <NA> 473 1227.8333 1870 6
O R possui funções próprias para realizar regressões. A principal delas é a função lm() e sua sintaxe é bastante direta e apenas um argumento é necessário: a fórmula.
lm(var_explicada ~ log(var_que_explica1) + (var_que_explica2 ^ var_que_explica3))
A fórmula, por sua vez pode ser escrita de várias maneiras.
| Sintaxe | modelo |
|---|---|
| Y ~ A | Y = ß0 + ß1A |
| Y ~ -1 + A | Y = ß1A |
| Y ~ A + I(A^2) | Y = ß0+ ß1A + ß2A2 |
| Y ~ A + B | Y = ß0+ ß1A + ß2B |
| Y ~ A:B | Y = ß0 + ß1AB |
| Y ~ A*B | Y = ß0 + ß1A + ß2B + ß3AB |
| Y ~ (A + B + C)^2 | Y = ß0+ ß1A + ß2B + ß3C + ß4AB + ß5AC + ß6AC |
Para demonstrar o uso da função lm() vamos criar uma variável aleatória var1 e o erro do modelo e vamos construir um y baseado neles. Após, pediremos ao R que defina a relação entre a variável var1 e y e guarde em modelo.
set.seed(1)
var1 <- rnorm(50) # apenas 50 observações
erro <- rnorm(50)
y = 2.3 * var1 + erro
modelo <- lm(y ~ log(var1))
## Warning in log(var1): NaNs produzidos
Mas o que é um modelo? Uma fórmula? E os parâmetros? Ele também tem erros, medidas de precisão como o R², etc. Qual delas estará guardada em modelo? Vamos descobrir!
print(modelo)
summary(modelo)
str(modelo)
A visualização do modelo
plot(log(var1), y)
## Warning in log(var1): NaNs produzidos
abline(modelo, col = 'red', lwd = 2)
Outro passo importante na análise de modelos é verificar algumas de suas propriedades e os pressupostos são respeitados.
layout(matrix(1:4, 2, 2))
plot(modelo)
Agora é sua hora de criar modelos para o lucro das instituições de ensino superior privada em função do número de profissionais em determinado nível de ensino. Crie um modelo para cada um dos níveis de ensino.
Estamos usando funções no R desde que começamos. Mas o que são funções? As funções são comandos que recebem parâmetros de entrada, fazem alguma coisa e retornam algum resultado.
saida <- função(entrada)
A função pode ser divida em três partes: nome, argumentos e corpo. Um das grande vantagens do R enquanto linguagem de programação é que podemos criar nossas prórpias funções.
nome <- function(argumento1, argumento2, ...) {
# corpo da função
# aqui algo é feito com argumento1 e argumento2
}
Vamos criar uma função que recebe um vetor, conta os NAs dele e retorna este número.
conta_na <- function(x) {
sum(is.na(x))
}
conta_na(c(NA, 3, NA, 2, 4, 5, NA))
## [1] 3
conta_na(1:6)
## [1] 0
Evita copiar e colar código, o que diminui chance de erro. Além disso, quando você descobrir que há um erro em seu código (e você vai!), só uma alteração será necessária, aquela que define a função.
Você ainda pode usar um função em outra situação. Digamos que você tenha feito esta função conta_na() para fazer uma análise da PNAD. Quando você for analisar os dados da RAIS, não vai precisar escrever outra vez o mesmo código.
Há uma idéia de que você deve escrever uma função quando executa uma tarefa pela terceira vez. Se você está precisando repetir alguma rotina e está em dúvida se deve escrever um função, provavelmente é porque deve escrevê-la.
As funções podem ter quantos argumentos você achar necessário. Mas você pode não querer usar todos argumentos em cada vez que rodar a função. Para isso existem os argumentos padrão: se há um padrão para determinado argumento e você não o expecifica, o padrão é utilizado. Lembra das funções read.table() e read.csv2()? A diferença entre elas está nos argumentos padrão.
elevado <- function(x, n = 2) {
x ^ n
}
elevado(2, 4)
## [1] 16
elevado(2) # ambas funcionam
## [1] 4
Ainda vou escrever
Uma função é recursiva quando ela chama a si própria até que ela não chame mais. Funções recursivas são muito mais eficientes Exemplo: função que conta de um número até zero.
ate_0 <- function(x) {
if (x >= 0) {
print(x)
ate_0(x-1)
} else return()
}
Não se esqueça de especificar um caso base (onde decide parar de se chamar). Caso contrário, ocorrerá um loop infinito (que pode ser cancelado com a tacla ESQ .
Já parou para pensar como o + ou o \ funcionam no R? Sim! Eles também são funções. Isso quer dizer que você pode criar seus próprios operadores se quiser.
`%Op%` <- function(x, funcao) funcao(x)
1:10 %Op% mean
## [1] 5.5
Ainda vai ter texto
Já observou dependendo do objeto o R decide imprimi-lo diferente, ou mostrar o sumário diferente? O R faz isso baseado na classe do objeto que passamos a ele. Há, em alguns casos, distintos métodos para classes diferentes. Veja methods(print).
O que acontece se usamos a conta_na() em um data frame? Ele retorna o número todos de NA em todo o data frame. Mas isso não é tão útil. Queremos que, quando o argumento da função for um data frame, ele diga quantos NAs cada variável tem. Como podemos fazer isso?
conta_na <- function(x) {
UseMethod("conta_na", x)
} # sobreescrevemos a função
conta_na.data.frame <- function(df) {
sapply(df, conta_na)
}
conta_na.default <- function(x) {
sum(is.na(x))
} # temos que redefinir
Este texto foi originalmente publicado no blog Análise Real.
Um loop utilizando for() no R tem a seguinte estrutura básica:
for(i in conjunto_de_valores){
# comandos que
# serão repetidos
}
for seguido de parênteses e chaves;i) e um conjunto de valores que será iterado (conjunto_de_valores).Em outras palavras, no comando acima estamos dizendo que para cada elemento i contido no conjunto_de_valores iremos executar os comandos que estão dentro das chaves.
Para facilitar o entendimento, vejamos dois exemplos muito simples. Primeiro, vamos imprimir na tela os números de 1 a 5.
for(i in 1:5){
print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
Agora, vamos imprimir na tela as 5 primeiras letras do alfabeto (o R já vem com um vetor com as letras do alfabeto: letters).
for(i in 1:5){
print(letters[i])
}
## [1] "a"
## [1] "b"
## [1] "c"
## [1] "d"
## [1] "e"
No mesmo exemplo, acima, ao invés correr o loop no índice de inteiros 1:5, vamos iterar diretamente sobre os primeiros 5 elementos do vetor letters:
for(letra in letters[1:5]){
print(letra)
}
## [1] "a"
## [1] "b"
## [1] "c"
## [1] "d"
## [1] "e"
Uma função bastante útil ao fazer loops é a função seg_along(). Ela cria um vetor de inteiros com índices para acompanhar o objeto.
# criando um vetor de exemplo
set.seed(119)
x <- rnorm(10)
# inteiros de 1 a 10
seq_along(x)
## [1] 1 2 3 4 5 6 7 8 9 10
Também é possível criar um vetor de inteiros do tamanho do objeto fazendo uma sequência de 1 até length(x):
1:length(x)
## [1] 1 2 3 4 5 6 7 8 9 10
Entretanto, a vantagem de seq_along() é que quando o vetor é vazio, ela retorna um vetor vazio e, deste modo, o loop não é executado (o que é o comportamento correto).
Já a sequência 1:length(x) retorna a sequência 1:0, isto é, uma sequência decrescente de 1 até 0, e loop é executado nestes valores.
Vejamos:
# cria vetor vazio
x <- numeric(0)
# 1:length(x)
# note que o loop é executado (o que é errado)
for(i in 1:length(x)) print(i)
## [1] 1
## [1] 0
# seq_along
# note que o loop não é executado (o que é correto)
for(i in seq_along(x)) print(i)
Como vimos, o R é vetorizado. Muitas vezes, quando você pensar que precisa usar um loop, ao pensar melhor, descobrirá que não precisa. Em geral é possível resolver o problema de maneira vetorizada e usando funções nativas do R.
Para quem está aprendendo a programar diretamente com o R, isso é algo que virá naturalmente. Todavia, para quem já sabia programar em outras linguagens de programação como C pode ser difícil se acostumar a pensar desta maneira.
Vejamos um exemplo trivial. Suponha que você queira dividir os valores de um vetor x por 10. Se o R não fosse vetorizado, você teria que fazer algo como:
# criando vetor de exemplo
x <- 10:20
# divide cada elemento por 10
for(i in seq_along(x))
x[i] <- x[i]/10
# resultado
x
## [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0
Mas o R é vetorizado e, portanto, este é o tipo de loop que não faz sentido na linguagem. É muito mais rápido e fácil de enteder escrever simplesmente x/10.
# recriando vetor de exemplo
x <- 10:20
# divide cada elemento por 10
x <- x/10
x
## [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0
Vejamos um caso um pouco mais complicado. Suponha que você queira, gerar um passeio aleatório com um algoritmo simples: a cada período você pode andar para frente (+1) ou para trás (-1) com probabilidades iguais.
set.seed(1)
# número de passos
n <- 1000
# vetor para armazenar o passeio aleatório
passeio <- numeric(n)
# primeiro passo
passeio[1] <- sample(c(-1, 1), 1)
# demais passos
for(i in 2:n){
# passo i é o onte você estava (passeio[i-1])
# mais o passo seguinte
passeio[i] <- passeio[i - 1] + sample(c(-1, 1), 1)
}
É possível fazer tudo isso com apenas uma linha de maneira vetorizada e bem mais eficiente: crie todos os n passos de uma vez e faça a soma acumulada.
set.seed(1)
passeio2 <- cumsum(sample(c(-1, 1), n, TRUE))
# verifica se são iguais
all.equal(passeio, passeio2)
## [1] TRUE
Então, você deve estar se perguntando: não é para usar loops nunca?
Não é isso. Em algumas situações loops são inevitáveis e podem inclusive ser mais fáceis de ler e de entender. O ponto aqui é apenas lembrá-lo de explorar a vetorização do R.
Voltando ao nosso exemplo do passeio aleatório, você deve ter notado a linha passeio <- numeric(n) em que criamos um vetor numérico para ir armazenando os resultados das iterações. Discutamos um pouco mais esse ponto.
Um erro bastante comum de quem está começando a programar em R é crescer objetos durante o loop. Isto tem um impacto substancial na performance do seu programa! Sempre que possível, crie um objeto, antes de iniciar o loop, para armazenar os resultados de cada iteração.
Vejamos um exemplo um pouco mais elaborado: vamos calcular os n primeiros números da sequência de Fibonacci: \(F_1 = 0, F_2 = 1, F_3 = 1, F_4 = 2, F_5 = 3, F_6 = 5, F_7 = 8, F_8 = 13, F_9 = 21 ...\)
Note que a sequência de Fibonacci pode ser definida da seguinte forma, os primeiros dois números são 0 e 1, isto é, \(F_1 = 0, F_2 = 1\). A partir daí, os números subsequentes são a soma dos dois números anteriores, isto é, \(F_i = F_{i-1} + F_{i-2}\) para todo \(i > 2\).
Vejamos uma forma de implementar isto no R usando for() e criando um vetor para armazenar os resultados:
n <- 9
# crie um vetor de tamanho n
# para armazenar os n resultados
fib <- numeric(n)
# comece definindo as condições iniciais
# F1 = 0 e F2 = 1
fib[1] <- 0
fib[2] <- 1
# Agora para todo i > 2
# calculamos Fi = F(i-1) + F(i - 2)
for(i in 3:n){
fib[i] <- fib[i - 1] + fib[i - 2]
}
# conferindo resultado
fib
## [1] 0 1 1 2 3 5 8 13 21
Vamos comparar a performance deste código com outro sem pré-alocar um vetor de resultados. Primeiro, transformemos nosso loop em uma função:
fib <- function(n){
# vetor para armazenar resultados
fib <- numeric(n)
# condições iniciais
fib[1] <- 0
fib[2] <- 1
# calculandos o números de 3 a n
for(i in 3:n){
fib[i] <- fib[i - 1] + fib[i - 2]
}
return(fib)
}
Agora, criemos outra função em que o vetor fib cresce a cada iteração:
fib_sem_pre_alocar <- function(n){
# condições iniciais
fib <- 0
fib <- c(fib, 1)
# calculandos o números de 3 a n
for(i in 3:n){
fib <- c(fib, fib[i - 1] + fib[i - 2])
}
return(fib)
}
Comparando as duas implementações:
library(microbenchmark)
set.seed(5)
microbenchmark(fib(5000), fib_sem_pre_alocar(5000))
## Unit: milliseconds
## expr min lq mean median uq max neval
## fib(5000) 8.5 8.8 9.2 9.1 9.4 11 100
## fib_sem_pre_alocar(5000) 58.4 60.7 67.5 62.2 64.4 99 100
Note que quanto maior o número de simulações, maior a queda na performance: com n = 5000 a função fib_sem_pre_alocar() chega a ser mais de 10 vezes mais lenta do que a função fib().
Vamos calcular a média de cada uma das colunas do data.frame mtcars usando loops.
Para isso precisamos: (i) saber quantas colunas existem no data.frame; (ii) criar um vetor para armazenar os resultados; (iii) nomear o vetor de resultados com os nomes das colunas; e (iv) fazer um loop para cada coluna.
# (i) quantas colunas no data.frame
n <- ncol(mtcars)
# (ii) vetor para armazenar resultados
medias <- numeric(n)
# (iii) nomeando vetor com nomes das colunas
names(medias) <- colnames(mtcars)
# (iv) loop para cada coluna
for(i in seq_along(mtcars)){
medias[i] <- mean(mtcars[,i])
}
# resultado final
medias
## mpg cyl disp hp drat wt qsec vs am gear carb
## 20.09 6.19 230.72 146.69 3.60 3.22 17.85 0.44 0.41 3.69 2.81
Gastamos várias linhas para fazer essa simples operação. Como já vimos, é bastante fácil fazer isso no R com apenas uma linha:
sapply(mtcars, mean)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 20.09 6.19 230.72 146.69 3.60 3.22 17.85 0.44 0.41 3.69 2.81
Imagine que não existisse a função sapply() no R. Se quiséssemos aplicar outra função para cada coluna, teríamos que copiar e colar todo o código novamente, certo?
Sim, você poderia fazer isso, mas não seria uma boa prática. Neste caso, como já vimos, o ideal seria criar uma função.
Façamos, portanto, uma função que nos permita aplicar uma fução arbitrária nas colunas de um data.frame.
meu_sapply <- function(x, funcao){
n <- length(x)
resultado <- numeric(n)
names(resultado) <- names(x)
for(i in seq_along(x)){
resultado[i] <- funcao(x[[i]])
}
return(resultado)
}
Perceba que ficou bastante simples percorrer todas as colunas de um data.frame para aplicar a função que você quiser:
meu_sapply(mtcars, mean)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 20.09 6.19 230.72 146.69 3.60 3.22 17.85 0.44 0.41 3.69 2.81
meu_sapply(mtcars, sd)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 6.03 1.79 123.94 68.56 0.53 0.98 1.79 0.50 0.50 0.74 1.62
meu_sapply(mtcars, max)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 33.9 8.0 472.0 335.0 4.9 5.4 22.9 1.0 1.0 5.0 8.0
meu_sapply(mtcars, min)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 10.4 4.0 71.1 52.0 2.8 1.5 14.5 0.0 0.0 3.0 1.0
É isso o que as funções da família apply são: são funções que fazem loops para você. Elas automaticamente cuidam de toda a parte chata do loop como, por exemplo, criar um objeto de tamanho correto para pré-alocar os resultados. Além disso, em grande parte das vezes essas funções serão mais eficientes do que se você mesmo fizer a implementação.
Por curiosidade, vamos comparar a eficiência do sapply() do R com meu_sapply()
microbenchmark(sapply(mtcars, mean), meu_sapply(mtcars, mean))
## Unit: microseconds
## expr min lq mean median uq max neval
## sapply(mtcars, mean) 68 71 88 74 87 358 100
## meu_sapply(mtcars, mean) 131 135 157 147 158 391 100
As funções que você irá implementar aqui, usando for(), serão até mais de 100 vezes mais lentas do que as funções nativas do R. Estes exercícios são para você treinar a construção de loops, um pouco de lógica de programação, e entender o que as funções do R estão fazendo de maneira geral por debaixo dos panos.
Crie uma função que encontre o máximo de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função max() do R. Sua função é quantas vezes mais lenta?
Crie uma função que calcule o fatorial de n (use for() na sua função). Compare os resultados e a performance de sua implementação com a função factorial() do R. Sua função é quantas vezes mais lenta?
Crie uma função que calcule a soma de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função sum() do R. Sua função é quantas vezes mais lenta?
Crie uma função que calcule a soma acumulada de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função cumsum() do R. Sua função é quantas vezes mais lenta?
Respostas
Criando vetor aleatório para comparar as funções:
# cria vetor para comparar resultados
set.seed(123)
x <- rnorm(100)
# Pacote para comparar resultados
library(microbenchmark)
Resposta sugerida ex-1:
# 1) loop para encontrar máximo
max_loop <- function(x){
max <- x[1]
for(i in 2:length(x)){
if(x[i] > max){
max <- x[i]
}
}
return(max)
}
all.equal(max(x), max_loop(x))
## [1] TRUE
microbenchmark(max(x), max_loop(x))
## Unit: nanoseconds
## expr min lq mean median uq max neval
## max(x) 0 1 243 1 467 2799 100
## max_loop(x) 62049 70447 72556 72780 73713 130163 100
Resposta sugerida ex-2:
# 2) loop para fatorial
fatorial <- function(n){
fat <- 1
for(i in 1:n){
fat <- fat*i
}
return(fat)
}
all.equal(factorial(10), fatorial(10))
## [1] TRUE
microbenchmark(factorial(10), fatorial(10))
## Warning in microbenchmark(factorial(10), fatorial(10)): Could not measure a positive execution time
## for 2 evaluations.
## Unit: nanoseconds
## expr min lq mean median uq max neval
## factorial(10) 0 0 182 1 467 2333 100
## fatorial(10) 5132 5599 8296 6066 7932 39189 100
Resposta sugerida ex-3:
# 3) loop para soma
soma <- function(x){
n <- length(x)
soma <- numeric(n)
soma <- x[1]
for(i in 2:n){
soma <- x[i] + soma
}
return(soma)
}
all.equal(soma(x), sum(x))
## [1] TRUE
microbenchmark(sum(x), soma(x))
## Warning in microbenchmark(sum(x), soma(x)): Could not measure a positive execution time for 6
## evaluations.
## Unit: nanoseconds
## expr min lq mean median uq max neval
## sum(x) 0 0 206 1 467 1866 100
## soma(x) 57384 66715 75625 68114 74412 133896 100
Resposta sugerida ex-4:
# 4) loop para soma acumulada
soma_acumulada <- function(x){
n <- length(x)
soma <- numeric(n)
soma[1] <- x[1]
for(i in 2:n){
soma[i] <- x[i] + soma[i-1]
}
return(soma)
}
all.equal(soma_acumulada(x), cumsum(x))
## [1] TRUE
microbenchmark(cumsum(x), soma_acumulada(x))
## Unit: nanoseconds
## expr min lq mean median uq max neval
## cumsum(x) 0 467 714 467 934 4666 100
## soma_acumulada(x) 166552 178683 215683 187313 252861 471665 100
Este texto foi originalmente publicado no blog Análise Real.
Um loop utilizando for() no R tem a seguinte estrutura básica:
for(i in conjunto_de_valores){
# comandos que
# serão repetidos
}
for seguido de parênteses e chaves;i) e um conjunto de valores que será iterado (conjunto_de_valores).Em outras palavras, no comando acima estamos dizendo que para cada elemento i contido no conjunto_de_valores iremos executar os comandos que estão dentro das chaves.
Para facilitar o entendimento, vejamos dois exemplos muito simples. Primeiro, vamos imprimir na tela os números de 1 a 5.
for(i in 1:5){
print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
Agora, vamos imprimir na tela as 5 primeiras letras do alfabeto (o R já vem com um vetor com as letras do alfabeto: letters).
for(i in 1:5){
print(letters[i])
}
## [1] "a"
## [1] "b"
## [1] "c"
## [1] "d"
## [1] "e"
No mesmo exemplo, acima, ao invés correr o loop no índice de inteiros 1:5, vamos iterar diretamente sobre os primeiros 5 elementos do vetor letters:
for(letra in letters[1:5]){
print(letra)
}
## [1] "a"
## [1] "b"
## [1] "c"
## [1] "d"
## [1] "e"
Uma função bastante útil ao fazer loops é a função seg_along(). Ela cria um vetor de inteiros com índices para acompanhar o objeto.
# criando um vetor de exemplo
set.seed(119)
x <- rnorm(10)
# inteiros de 1 a 10
seq_along(x)
## [1] 1 2 3 4 5 6 7 8 9 10
Também é possível criar um vetor de inteiros do tamanho do objeto fazendo uma sequência de 1 até length(x):
1:length(x)
## [1] 1 2 3 4 5 6 7 8 9 10
Entretanto, a vantagem de seq_along() é que quando o vetor é vazio, ela retorna um vetor vazio e, deste modo, o loop não é executado (o que é o comportamento correto).
Já a sequência 1:length(x) retorna a sequência 1:0, isto é, uma sequência decrescente de 1 até 0, e loop é executado nestes valores.
Vejamos:
# cria vetor vazio
x <- numeric(0)
# 1:length(x)
# note que o loop é executado (o que é errado)
for(i in 1:length(x)) print(i)
## [1] 1
## [1] 0
# seq_along
# note que o loop não é executado (o que é correto)
for(i in seq_along(x)) print(i)
Como vimos, o R é vetorizado. Muitas vezes, quando você pensar que precisa usar um loop, ao pensar melhor, descobrirá que não precisa. Em geral é possível resolver o problema de maneira vetorizada e usando funções nativas do R.
Para quem está aprendendo a programar diretamente com o R, isso é algo que virá naturalmente. Todavia, para quem já sabia programar em outras linguagens de programação como C pode ser difícil se acostumar a pensar desta maneira.
Vejamos um exemplo trivial. Suponha que você queira dividir os valores de um vetor x por 10. Se o R não fosse vetorizado, você teria que fazer algo como:
# criando vetor de exemplo
x <- 10:20
# divide cada elemento por 10
for(i in seq_along(x))
x[i] <- x[i]/10
# resultado
x
## [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0
Mas o R é vetorizado e, portanto, este é o tipo de loop que não faz sentido na linguagem. É muito mais rápido e fácil de enteder escrever simplesmente x/10.
# recriando vetor de exemplo
x <- 10:20
# divide cada elemento por 10
x <- x/10
x
## [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0
Vejamos um caso um pouco mais complicado. Suponha que você queira, gerar um passeio aleatório com um algoritmo simples: a cada período você pode andar para frente (+1) ou para trás (-1) com probabilidades iguais.
set.seed(1)
# número de passos
n <- 1000
# vetor para armazenar o passeio aleatório
passeio <- numeric(n)
# primeiro passo
passeio[1] <- sample(c(-1, 1), 1)
# demais passos
for(i in 2:n){
# passo i é o onte você estava (passeio[i-1])
# mais o passo seguinte
passeio[i] <- passeio[i - 1] + sample(c(-1, 1), 1)
}
É possível fazer tudo isso com apenas uma linha de maneira vetorizada e bem mais eficiente: crie todos os n passos de uma vez e faça a soma acumulada.
set.seed(1)
passeio2 <- cumsum(sample(c(-1, 1), n, TRUE))
# verifica se são iguais
all.equal(passeio, passeio2)
## [1] TRUE
Então, você deve estar se perguntando: não é para usar loops nunca?
Não é isso. Em algumas situações loops são inevitáveis e podem inclusive ser mais fáceis de ler e de entender. O ponto aqui é apenas lembrá-lo de explorar a vetorização do R.
Voltando ao nosso exemplo do passeio aleatório, você deve ter notado a linha passeio <- numeric(n) em que criamos um vetor numérico para ir armazenando os resultados das iterações. Discutamos um pouco mais esse ponto.
Um erro bastante comum de quem está começando a programar em R é crescer objetos durante o loop. Isto tem um impacto substancial na performance do seu programa! Sempre que possível, crie um objeto, antes de iniciar o loop, para armazenar os resultados de cada iteração.
Vejamos um exemplo um pouco mais elaborado: vamos calcular os n primeiros números da sequência de Fibonacci: \(F_1 = 0, F_2 = 1, F_3 = 1, F_4 = 2, F_5 = 3, F_6 = 5, F_7 = 8, F_8 = 13, F_9 = 21 ...\)
Note que a sequência de Fibonacci pode ser definida da seguinte forma, os primeiros dois números são 0 e 1, isto é, \(F_1 = 0, F_2 = 1\). A partir daí, os números subsequentes são a soma dos dois números anteriores, isto é, \(F_i = F_{i-1} + F_{i-2}\) para todo \(i > 2\).
Vejamos uma forma de implementar isto no R usando for() e criando um vetor para armazenar os resultados:
n <- 9
# crie um vetor de tamanho n
# para armazenar os n resultados
fib <- numeric(n)
# comece definindo as condições iniciais
# F1 = 0 e F2 = 1
fib[1] <- 0
fib[2] <- 1
# Agora para todo i > 2
# calculamos Fi = F(i-1) + F(i - 2)
for(i in 3:n){
fib[i] <- fib[i - 1] + fib[i - 2]
}
# conferindo resultado
fib
## [1] 0 1 1 2 3 5 8 13 21
Vamos comparar a performance deste código com outro sem pré-alocar um vetor de resultados. Primeiro, transformemos nosso loop em uma função:
fib <- function(n){
# vetor para armazenar resultados
fib <- numeric(n)
# condições iniciais
fib[1] <- 0
fib[2] <- 1
# calculandos o números de 3 a n
for(i in 3:n){
fib[i] <- fib[i - 1] + fib[i - 2]
}
return(fib)
}
Agora, criemos outra função em que o vetor fib cresce a cada iteração:
fib_sem_pre_alocar <- function(n){
# condições iniciais
fib <- 0
fib <- c(fib, 1)
# calculandos o números de 3 a n
for(i in 3:n){
fib <- c(fib, fib[i - 1] + fib[i - 2])
}
return(fib)
}
Comparando as duas implementações:
library(microbenchmark)
set.seed(5)
microbenchmark(fib(5000), fib_sem_pre_alocar(5000))
## Unit: milliseconds
## expr min lq mean median uq max neval
## fib(5000) 8.5 8.8 9.2 9.1 9.4 11 100
## fib_sem_pre_alocar(5000) 58.4 60.7 67.5 62.2 64.4 99 100
Note que quanto maior o número de simulações, maior a queda na performance: com n = 5000 a função fib_sem_pre_alocar() chega a ser mais de 10 vezes mais lenta do que a função fib().
Vamos calcular a média de cada uma das colunas do data.frame mtcars usando loops.
Para isso precisamos: (i) saber quantas colunas existem no data.frame; (ii) criar um vetor para armazenar os resultados; (iii) nomear o vetor de resultados com os nomes das colunas; e (iv) fazer um loop para cada coluna.
# (i) quantas colunas no data.frame
n <- ncol(mtcars)
# (ii) vetor para armazenar resultados
medias <- numeric(n)
# (iii) nomeando vetor com nomes das colunas
names(medias) <- colnames(mtcars)
# (iv) loop para cada coluna
for(i in seq_along(mtcars)){
medias[i] <- mean(mtcars[,i])
}
# resultado final
medias
## mpg cyl disp hp drat wt qsec vs am gear carb
## 20.09 6.19 230.72 146.69 3.60 3.22 17.85 0.44 0.41 3.69 2.81
Gastamos várias linhas para fazer essa simples operação. Como já vimos, é bastante fácil fazer isso no R com apenas uma linha:
sapply(mtcars, mean)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 20.09 6.19 230.72 146.69 3.60 3.22 17.85 0.44 0.41 3.69 2.81
Imagine que não existisse a função sapply() no R. Se quiséssemos aplicar outra função para cada coluna, teríamos que copiar e colar todo o código novamente, certo?
Sim, você poderia fazer isso, mas não seria uma boa prática. Neste caso, como já vimos, o ideal seria criar uma função.
Façamos, portanto, uma função que nos permita aplicar uma fução arbitrária nas colunas de um data.frame.
meu_sapply <- function(x, funcao){
n <- length(x)
resultado <- numeric(n)
names(resultado) <- names(x)
for(i in seq_along(x)){
resultado[i] <- funcao(x[[i]])
}
return(resultado)
}
Perceba que ficou bastante simples percorrer todas as colunas de um data.frame para aplicar a função que você quiser:
meu_sapply(mtcars, mean)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 20.09 6.19 230.72 146.69 3.60 3.22 17.85 0.44 0.41 3.69 2.81
meu_sapply(mtcars, sd)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 6.03 1.79 123.94 68.56 0.53 0.98 1.79 0.50 0.50 0.74 1.62
meu_sapply(mtcars, max)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 33.9 8.0 472.0 335.0 4.9 5.4 22.9 1.0 1.0 5.0 8.0
meu_sapply(mtcars, min)
## mpg cyl disp hp drat wt qsec vs am gear carb
## 10.4 4.0 71.1 52.0 2.8 1.5 14.5 0.0 0.0 3.0 1.0
É isso o que as funções da família apply são: são funções que fazem loops para você. Elas automaticamente cuidam de toda a parte chata do loop como, por exemplo, criar um objeto de tamanho correto para pré-alocar os resultados. Além disso, em grande parte das vezes essas funções serão mais eficientes do que se você mesmo fizer a implementação.
Por curiosidade, vamos comparar a eficiência do sapply() do R com meu_sapply()
microbenchmark(sapply(mtcars, mean), meu_sapply(mtcars, mean))
## Unit: microseconds
## expr min lq mean median uq max neval
## sapply(mtcars, mean) 68 71 88 74 87 358 100
## meu_sapply(mtcars, mean) 131 135 157 147 158 391 100
As funções que você irá implementar aqui, usando for(), serão até mais de 100 vezes mais lentas do que as funções nativas do R. Estes exercícios são para você treinar a construção de loops, um pouco de lógica de programação, e entender o que as funções do R estão fazendo de maneira geral por debaixo dos panos.
Crie uma função que encontre o máximo de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função max() do R. Sua função é quantas vezes mais lenta?
Crie uma função que calcule o fatorial de n (use for() na sua função). Compare os resultados e a performance de sua implementação com a função factorial() do R. Sua função é quantas vezes mais lenta?
Crie uma função que calcule a soma de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função sum() do R. Sua função é quantas vezes mais lenta?
Crie uma função que calcule a soma acumulada de um vetor (use for() na sua função). Compare os resultados e a performance de sua implementação com a função cumsum() do R. Sua função é quantas vezes mais lenta?
Respostas
Criando vetor aleatório para comparar as funções:
# cria vetor para comparar resultados
set.seed(123)
x <- rnorm(100)
# Pacote para comparar resultados
library(microbenchmark)
Resposta sugerida ex-1:
# 1) loop para encontrar máximo
max_loop <- function(x){
max <- x[1]
for(i in 2:length(x)){
if(x[i] > max){
max <- x[i]
}
}
return(max)
}
all.equal(max(x), max_loop(x))
## [1] TRUE
microbenchmark(max(x), max_loop(x))
## Unit: nanoseconds
## expr min lq mean median uq max neval
## max(x) 0 1 243 1 467 2799 100
## max_loop(x) 62049 70447 72556 72780 73713 130163 100
Resposta sugerida ex-2:
# 2) loop para fatorial
fatorial <- function(n){
fat <- 1
for(i in 1:n){
fat <- fat*i
}
return(fat)
}
all.equal(factorial(10), fatorial(10))
## [1] TRUE
microbenchmark(factorial(10), fatorial(10))
## Warning in microbenchmark(factorial(10), fatorial(10)): Could not measure a positive execution time
## for 2 evaluations.
## Unit: nanoseconds
## expr min lq mean median uq max neval
## factorial(10) 0 0 182 1 467 2333 100
## fatorial(10) 5132 5599 8296 6066 7932 39189 100
Resposta sugerida ex-3:
# 3) loop para soma
soma <- function(x){
n <- length(x)
soma <- numeric(n)
soma <- x[1]
for(i in 2:n){
soma <- x[i] + soma
}
return(soma)
}
all.equal(soma(x), sum(x))
## [1] TRUE
microbenchmark(sum(x), soma(x))
## Warning in microbenchmark(sum(x), soma(x)): Could not measure a positive execution time for 6
## evaluations.
## Unit: nanoseconds
## expr min lq mean median uq max neval
## sum(x) 0 0 206 1 467 1866 100
## soma(x) 57384 66715 75625 68114 74412 133896 100
Resposta sugerida ex-4:
# 4) loop para soma acumulada
soma_acumulada <- function(x){
n <- length(x)
soma <- numeric(n)
soma[1] <- x[1]
for(i in 2:n){
soma[i] <- x[i] + soma[i-1]
}
return(soma)
}
all.equal(soma_acumulada(x), cumsum(x))
## [1] TRUE
microbenchmark(cumsum(x), soma_acumulada(x))
## Unit: nanoseconds
## expr min lq mean median uq max neval
## cumsum(x) 0 467 714 467 934 4666 100
## soma_acumulada(x) 166552 178683 215683 187313 252861 471665 100
Remuneração por sexo e cor
O ggplot2 é mais do que um pacote para fazer gráficos; ele é uma tentativa (muito bem sucedida) trazer para o dia-a-dia dos técnicos uma gramática dos gráficos.
Através dela podemos definir sistematicamente quais são os componentes de um gráficos e como eles se interelacionam.
Veja mais informações em http://docs.ggplot2.org/.
| elemento | exemplos |
|---|---|
| dados (informação)* | seguro, fiscalizações |
| (a)estética* | cor, formato |
| geometrias* | barra, ponto |
| estatísticas | mediana, máximo |
| facetas | facetas |
| coordenadas | polar, cartesiana |
| t(h)emas | eixos, título |
A primeira etapa da construção de um gráfico é ter os dados que serão representados graficamente. Vamos carregar os dados dos vínculos da RAIS em 2014 para Santa Catarina
vinculos_2014 <- readRDS('Microdados/vinculos_SC_2014.RDS')
vinculos_2014 <- as.data.frame(vinculos_2014)
# Para tirar a classe data.table,
# não apresentada neste curso
vinculos_2014 <- vinculos_2014[, c("Idade", "Mês Admissão",
"Raça Cor" , "Sexo Trabalhador",
"Vl Remun Média Nom", "Qtd Hora Contr")]
Ainda precisamos melhorar um pouco a forma como os dados chegaram. Idade poderia ser mais útil em faixas; Mês Admissão, Cor e Sexo são variáveis categóricas.
library(dplyr)
vinculos_2014 <- vinculos_2014 %>%
mutate(Admissão = factor(`Mês Admissão`,
labels = c('Não admitido ano', 'Jan', 'Fev',
'Mar', 'Abr', 'Mai', 'Jun', 'Jul',
'Ago', 'Set', 'Out', 'Nov', 'Dez')),
Cor = factor(`Raça Cor`,
labels = c("Indígena", "Branca", "Preta", "Amarela",
"Parda", "Não identificado", "Ignorado")),
Sexo = factor(`Sexo Trabalhador`,
labels = c("Masculino", "Feminino")),
Remuneração = as.numeric(gsub(pattern = ",", ".",
`Vl Remun Média Nom`)))
vinculos_2014$Jornada <- cut(vinculos_2014$`Qtd Hora Contr`,
breaks = c(-Inf, 20, 30, 40, Inf),
labels = c("Até 20", "Entre 20 e 30",
"Entre 30 e 40", "Mais de 40"))
set.seed(1) # semente de pseudo-aleatoriedade para reprodução
# Vamos reduzir os dados para 200 observações aleatórias
dados <- vinculos_2014[sample(1:nrow(vinculos_2014), 200), ]
Aqui apenas falar da diferença entre dado e estética.
Imagine que você fosse desenhar um gráfico. Como você decidiria até onde deve ir a barra ou onde ficariam os pontos? O computador também precisa de critérios para decidir como representar os dados, como a idade dos trabalhadores de Santa Catarina, em um gráfico.
Assim, a idade dos trabalhadores pode ser representada no eixo horizontal ou os grupos de idade podem aparecer como cores ou formato dos dados (menores de 30 triângulos, maiores quadrados).
Talvez copiar um slide do DataCamp seja meelhor
Explicar o que é mapear dados em elementos estéticos do gráfico
É diferente mapear uma estética e definir um atributo estético. Mapear um atributo estético é dizer que a cor representa
Falar da diferença de atribuir uma cor ou tamanho ao gráfico e mapeá-los.
Além de ter dados e mapeá-los em atributos estéticos, você deve escolher com que geometrias quer aprensentar seus dados.
As geometrias mais comuns são:
Pontos (diagrama de dispersão)
Barras
Linhas
Diagrama de baixa (boxplot)
Vamos ver como usar estas geometrias no ggplot2
ggplot(dados[dados$Remuneração < 10000, ], aes(Idade, Remuneração)) +
geom_point(aes(color = Jornada), size = 2, alpha = 0.6)
medias <- vinculos_2014 %>% group_by(Jornada, Sexo) %>%
summarise(Media = mean(Remuneração))
ggplot(medias, aes(x = Jornada, y = Media, fill = Sexo)) +
geom_bar(stat = "identity")
# gráficos de barras são geralmente usados em estatísticas sumarizadas
ggplot(vinculos_2014 %>% filter(Remuneração < 5000),
aes(x = Remuneração, fill = Sexo)) +
geom_histogram(binwidth = 500, alpha= 0.8) # definir qtd de intervalos(bins)
ggplot(vinculos_2014 %>% filter(Remuneração < 5000),
aes(x = Remuneração, fill = Sexo)) +
geom_density(alpha= 0.6)
medias <- dados %>% group_by(Jornada) %>%
summarise(Media = mean(Remuneração))
ggplot(dados[dados$Remuneração < 10000,], aes(Idade, Remuneração, col = Jornada)) +
geom_point(alpha = 0.6) +
geom_hline(aes(yintercept = Media, col = Jornada), data = medias, size = 1.5)
ggplot(vinculos_2014 %>% filter(Remuneração < 10000),
aes(x = Sexo, y = Remuneração, fill = Sexo)) +
geom_boxplot()
Toda geometria está vinculada a alguma estatística. Esta estatística pode ser identidade, isto é, o dados tal qual. Mas, observem, seria impossível desenhar algumas geometrias sem obter algumas estatísticas sobre os dados. O ggplot2 faz isso por trás das cenas para nós.
Por exemplo, para desenhar um diagrama de caida sobre precisa saber: i) o desvio padrão, ii), a mediana, iii) os 1º e 3º quartis. Um histograma, por sua vez, precisa i) definir classes de intervalos e ii) contar a quantidade de dados nestes intervalos.
Caso precisemos gerar as estatísticas, o ggplot2 tem a família de funções stat_*. Vejamos um exemplo
ggplot(dados, aes(x = Idade, y = Remuneração, col = Jornada)) +
geom_point() +
stat_smooth(geom = "smooth", method = "lm", level = 0.8)
# realizou calculos de regressão
menos_5 <- vinculos_2014[(vinculos_2014$Cor %in%
c("Preta", "Branca") &
vinculos_2014$Remuneração < 5000), ]
ggplot(menos_5, aes(Remuneração, fill = Sexo)) +
geom_density(alpha = 0.5) + facet_wrap(~Cor, dir = "v")
ggplot(menos_5, aes(Remuneração, fill = Sexo)) +
geom_density(alpha = 0.5) + facet_wrap(~Cor, dir = "v") +
theme_classic() +
scale_y_continuous(name = "Densidade", labels = c("0", "0,05%", "0,1%", "0,15%"), minor_breaks = NULL) +
scale_x_continuous(labels = c("R$ 0", "R$ 1.000", "R$ 2.000", "R$ 3.000", "R$ 4.000", "R$ 5.000"), minor_breaks = NULL) +
theme(legend.position = 'none')